package com.sudao.android.ball;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.AssetManager;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.MotionEvent;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.GestureDetector.OnGestureListener;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Vector;
import java.util.HashMap;

class GameView extends SurfaceView implements SurfaceHolder.Callback {

		private static final String LOG_CAT = "BrickBallView";

		public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate
		public static final int PHYS_PAD_SEC = 200;  // this constant also used for initializing PAD speed, mGoalSpeed
		public static final int PHYS_SPEED_HYPERSPACE = 180;
		public static final int PHYS_SPEED_MIN = 100;
		public static final int PHYS_SPEED_INIT = 120;
		public static final int PHYS_SPEED_MAX = 300;
		private static final double DELTA_SPEED_X = 0.01;
		private static final double DELTA_SPEED_Y = 0.01;

		private static final int ANGLE_MAX = 135;
		private static final int ANGLE_MIN = 45;

		public static final int STATE_LOSE_LEVEL = 0;
		public static final int STATE_LOSE_GAME  = 1;
		public static final int STATE_PAUSE = 2;
		public static final int STATE_READY = 3;
		public static final int STATE_RUNNING = 4;
		public static final int STATE_WIN_LEVEL = 5;
		public static final int STATE_WIN_GAME = 6;
		public static final int STATE_FIRST_START = 7;

		private static final int BOMB_BROKE_BRICK_COUNT = 5;
		private static final int POINTER_LENGTH = 20;

		private static final String KEY_MODE = "mMode";
		private static final String KEY_GOAL_WIDTH = "mGoalWidth";
		private static final String KEY_GOAL_X = "mGoalX";
		private static final String KEY_HEADING = "mHeading";
		private static final String KEY_STOPPED = "mStopped";
		private static final String KEY_BALL_HEIGHT = "mBallHeight";
		private static final String KEY_BALL_WIDTH = "mBallWidth";
		private static final String KEY_WINS = "mWins";
		private static final String	KEY_LASER_TOTAL_ELAPSE = "mLaserTotalElapse";
					
		private static final String KEY_BOUNCE_TIME = "mBounceTime";
		private static final String	KEY_BRICK_FALL_DOWN_TIME_ELAPSED = "mBrickFallDownTimeElapsed";

		private static final String KEY_SCORE = "mScore";
		private static final String	KEY_LIVES = "mLives";
		private static final String KEY_LEVEL = "mLevel";
		private static final String KEY_PAD_TYPE = "mPadType";
		private static final String KEY_OLD_PAD_TYPE = "mOldPadType";

		private static final String KEY_BRICK_COUNT = "mBrickCount";
		//matrix save/restore
		private static final String KEY_MATRIX_PREFIX = "matrix_";

		//bullets save/restore related keys
		private static final String KEY_BULLETS_SIZE = "bullets_size";
		private static final String KEY_BULLET_MAINKEY_PREFIX = "bullet_main_key_";

		//balls save/restore related keys
		private static final String KEY_BALLS_SIZE = "balls_size";
		private static final String KEY_BALL_MAINKEY_PREFIX = "ball_main_key_";
		
		//balls save/restore related keys
		private static final String KEY_PILLS_SIZE = "pills_size";
		private static final String KEY_PILL_MAINKEY_PREFIX = "pill_main_key_";
					
		private Bitmap mBackgroundImage;

		private int mCanvasHeight = this.getHeight();
		private int mCanvasWidth = this.getWidth();

		private int mGoalWidth;
		private int mGoalHeight;
		private int mGoalX;
		private int mGoalSpeed;

		public void setPadSpeed(int v) {
			mGoalSpeed = v;
		}

		private double mHeading;

		private int mBallHeight;

		private Drawable mBallImage[] = new Drawable[4];
		private int mBallWidth;
		private long mLastTime;

		private int mRotating;
		private SurfaceHolder mSurfaceHolder;

		/*private TransitionDrawable mBrickImage1;
		private TransitionDrawable mBrickImage2;
		private TransitionDrawable mBrickImage3;
		private TransitionDrawable mBrickImage4;
		private TransitionDrawable mBrickImage5;*/

		private Drawable mBrickImage1;
		private Drawable mBrickImage2;
		private Drawable mBrickImage3;
		private Drawable mBrickImage4;
		private Drawable mBrickImage5;

		private Drawable mArrowImage;
		private Drawable mPadImage;

		private int maxX = Level.maxX;
		private int maxY = Level.maxY;
		private int matrix[][] = new int[maxY][maxX];

		// private static final int BRICK_TYPE = 4;
		private static final int BRICK_TYPE_INVALID = 0;
		private static final int BRICK_TYPE_ONE = 1;
		private static final int BRICK_TYPE_TWO = 2;
		private static final int BRICK_TYPE_THREE = 3;
		private static final int BRICK_TYPE_FOUR = 4;
		private static final int BRICK_TYPE_FIVE = 5; //unbreakable brick except rocket hit it.

		private static final int PILL_FALL_DOWN_SPEED = 50;

		private static final int PILL_SCALE = 1;
		private static final int PILL_SLOW = 2;
		private static final int PILL_ROCKET = 3;
		private static final int PILL_LASER = 4;
		private static final int PILL_SPLIT = 5;
		private static final int PILL_STOP = 6;
		private static final int PILL_REVERSE = 7;
		private static final int PILL_WALL = 8;
		private static final int PILL_BOMB = 9;
		private static final int PILL_LIFE = 10;
		private static final int PILL_NOTHING = 11;

		private static final int ENTITY_BALL = PILL_NOTHING;
		private static final int ENTITY_LASER = PILL_LASER;
		private static final int ENTITY_ROCKET = PILL_ROCKET;
		private static final int ENTITY_BOMB = PILL_BOMB;

		private int mBrickWidth;
		private int mBrickHeight;
		private int mBrickOffsetY = 40;
		private int mBrickCount;

		private boolean mFiring;
		private double mLaserTotalElapse = 0.0;
		private static final double LASERSHOTTHRESHOLD = 0.4;

		private int mLives;
		private int mPadType; // value is PILL_XXX
		private int mOldPadType;

		private boolean mStopped;

		// brick hit 50 times begin fall down 
		private static final int BOUNCE_THRESHOLD = 10;
		private double FALL_DOWN_TIME_THRESHOLD = 1;
		private double FALL_DOWN_DISTANCE_THRESHOLD = 10;
		private int mBounceTime;
		private double mBrickFallDownTimeElapsed;

		// score 
		private static final int PILL_SCORE = 50;
		private static final int LASER_SCORE = 5;
		private static final int ROCKET_SCORE = 50;
		private static final int BOMB_SCORE = 5;
		private static final int BALL_SCORE = 10;
		
		private int mScore;

		private int mLevel;

		private int getBrickScore(int v) {
			return v / 10000;
		}

		private int getBasic(int v) {
			return v % 10000;
		}

		private int setBrickScore(int v) {
			return 100000 * getBrickType(v) + v;
		}

		private int setLaserBrickScoreAndType(int v) {
			if (getBrickType(v) == BRICK_TYPE_FIVE || getBrickType(v) == BRICK_TYPE_INVALID)
				return v;

			int score = getBrickScore(v);
			score -= LASER_SCORE;
			if (score == 0) {
				return 0;
			}
			int type = score / LASER_SCORE;
			if (type % 2 == 0) { // change into other type of brick
				--v;
			}
			v = score * 10000 + v % 10000;
			return v;
		}

		public class Ball {
			public double mDX;
			public double mDY;
			public double mX;
			public double mY;
			public boolean mlive;

			public static final String KEY_DX = "dx";
			public static final String KEY_DY = "dy";
			public static final String KEY_X = "x";
			public static final String KEY_Y = "y";
			public static final String KEY_LIVE = "live";

			Ball() {
				mDX = mDY = PHYS_SPEED_INIT;
				mX = mY = 0;
				mlive = false;
			}

			public void setValue(double dx, double dy, double x, double y,
					boolean b) {
				mDX = dx;
				mDY = dy;
				mX = x;
				mY = y;
				mlive = b;
			}

			public synchronized void saveState(Bundle map, String mainKey) {
				if (map != null) {
					map.putDouble(mainKey + KEY_DX, mDX);
					map.putDouble(mainKey + KEY_DY, mDY);
					map.putDouble(mainKey + KEY_X, mX);
					map.putDouble(mainKey + KEY_Y, mY);
					map.putBoolean(mainKey + KEY_LIVE, mlive);
				}
			}
		}

		public Vector<Ball> mBalls = new Vector<Ball>();

		public class Bullet {
			public static final int LASER = 0;
			public static final int ROCKET = 1;
			public static final int LASER_SPEED = 300;
			public static final int ROCKET_SPEED = 200;

			// these keys are used for saving/restoring Bullets when screen is rotated.
			public static final String KEY_TYPE = "type";
			public static final String KEY_X = "x";
			public static final String KEY_Y = "y";

			public Drawable bulletImage;
			public int type;
			public int x;
			public int y;
			public int w;
			public int h;
			public int speed;

			Bullet(int type, int x, int y) {
				this.type = type;
				this.x = x;
				this.y = y;
				this.speed = speed;
				if (type == LASER) {
					bulletImage = mContext.getResources().getDrawable(
							R.drawable.laser);
					speed = LASER_SPEED;
				} else if (type == ROCKET) {
					bulletImage = mContext.getResources().getDrawable(
							R.drawable.rocket);
					speed = ROCKET_SPEED;
				}
				w = bulletImage.getIntrinsicWidth();
				h = bulletImage.getIntrinsicHeight();
			}

			Bullet(Bullet copy) {
				new Bullet(copy.type, copy.x, copy.y);
			}

			public synchronized void saveState(Bundle map, String mainKey) {
				if (map != null) {
					map.putInt(mainKey + KEY_TYPE, type);
					map.putInt(mainKey + KEY_X, x);
					map.putInt(mainKey + KEY_Y, y);
				}
			}
		}

		public class Pill {
			public static final String KEY_TYPE = "type";
			public static final String KEY_X = "x";
			public static final String KEY_Y = "y";

			public Drawable pillImage;
			public int type;
			public int x;
			public int y;
			public int w;
			public int h;

			Pill(int type, int x, int y) {
				this.type = type;
				this.x = x;
				this.y = y;
				pillImage = getPillImageByType(type);
				w = pillImage.getIntrinsicWidth();
				h = pillImage.getIntrinsicHeight();
			}

			public synchronized void saveState(Bundle map, String mainKey) {
				if (map != null) {
					map.putInt(mainKey + KEY_TYPE, type);
					map.putInt(mainKey + KEY_X, x);
					map.putInt(mainKey + KEY_Y, y);
				}
			}
		}

		private void setSurfaceHolder(SurfaceHolder holder) {
			mSurfaceHolder = holder;
		}

		private ArrayList<Bullet> mBullets = new ArrayList<Bullet>();
		private ArrayList<Pill> mPills = new ArrayList<Pill>();

		private int getBrickType(int v) {
			return getBasic(v) % 10;
		}

		private int getStep(int v) {
			return (getBasic(v) / 10) % 10;
		}

		private int getPillType(int v) {
			return getBasic(v) / 100;
		}

		private Drawable getPillImageByType(int type) {
			Drawable d = null;
			Resources res = mContext.getResources();
			switch (type) {
			case PILL_SCALE:
				d = res.getDrawable(R.drawable.pill_scale);
				break;
			case PILL_SLOW:
				d = res.getDrawable(R.drawable.pill_slow);
				break;
			case PILL_ROCKET:
				d = res.getDrawable(R.drawable.pill_rocket);
				break;
			case PILL_LASER:
				d = res.getDrawable(R.drawable.pill_laser);
				break;
			case PILL_SPLIT:
				d = res.getDrawable(R.drawable.pill_split);
				break;
			case PILL_STOP:
				d = res.getDrawable(R.drawable.pill_stop);
				break;
			case PILL_REVERSE:
				d = res.getDrawable(R.drawable.pill_reverse);
				break;
			case PILL_WALL:
				d = res.getDrawable(R.drawable.pill_wall);
				break;
			case PILL_BOMB:
				d = res.getDrawable(R.drawable.pill_bomb);
				break;
			}
			return d;
		}

		private void loadResource() {
			Resources res = mContext.getResources(); 

            for (int i = 0; i < 4; i++) {
				mBallImage[i] = res.getDrawable(R.drawable.ball);
				mBalls.add(new Ball());
			}

			mArrowImage = res.getDrawable(R.drawable.arrow);
			mPadImage = res.getDrawable(R.drawable.pad);
			mGoalWidth = mPadImage.getIntrinsicWidth();
			mGoalHeight = mPadImage.getIntrinsicHeight();

			/*Drawable d1 = res.getDrawable(R.drawable.brick0_1);
			Drawable d2 = res.getDrawable(R.drawable.brick1_2);
			Drawable d3 = res.getDrawable(R.drawable.brick2_3);
			Drawable d4 = res.getDrawable(R.drawable.brick3_4);
			Drawable d5 = res.getDrawable(R.drawable.brick0_5);*/

			mBrickImage1 = res.getDrawable(R.drawable.brick1);
			mBrickImage2 = res.getDrawable(R.drawable.brick2);
			mBrickImage3 = res.getDrawable(R.drawable.brick3);
			mBrickImage4 = res.getDrawable(R.drawable.brick4);
			mBrickImage5 = res.getDrawable(R.drawable.brick5);

			mBrickWidth = mBrickImage1.getIntrinsicWidth();
			mBrickHeight = mBrickImage1.getIntrinsicHeight();

			/*if (d1 instanceof TransitionDrawable) {
				mBrickImage1 = (TransitionDrawable) d1;
				mBrickImage1.setCrossFadeEnabled(false);
			}
			if (d2 instanceof TransitionDrawable) {
				mBrickImage2 = (TransitionDrawable) d2;
				mBrickImage2.setCrossFadeEnabled(false);
			}
			if (d3 instanceof TransitionDrawable) {
				mBrickImage3 = (TransitionDrawable) d3;
				mBrickImage3.setCrossFadeEnabled(false);
			}
			if (d4 instanceof TransitionDrawable) {
				mBrickImage4 = (TransitionDrawable) d4;
				mBrickImage4.setCrossFadeEnabled(false);
			}
			if (d5 instanceof TransitionDrawable) {
				mBrickImage5 = (TransitionDrawable) d5;
				mBrickImage5.setCrossFadeEnabled(false);
			}*/

			mBackgroundImage = BitmapFactory.decodeResource(res,R.drawable.earthrise);
			mBallWidth = mBallImage[0].getIntrinsicWidth();
			mBallHeight = mBallImage[0].getIntrinsicHeight();

			gameReset();
			//initSounds();
		}

		private int getPadSpeedFromPreference() {
			return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(mContext)
				.getString(SoundControl.PREF_PAD_SPEED,
					SoundControl.DEFAULT_PREF_PAD_SPEED + ""));
		}

		private void cancelScale() {
			if (mOldPadType == PILL_SCALE && mPadType != PILL_SCALE) {
				mPadImage = mContext.getResources().getDrawable(R.drawable.pad);
				mGoalWidth = mPadImage.getIntrinsicWidth();
				mGoalHeight = mPadImage.getIntrinsicHeight();
				if (mGoalX <= 0) {
					mGoalX = 0;
				} else if (mGoalX + mGoalWidth >= mCanvasWidth - 1) {
					mGoalX = mCanvasWidth - 1 - mGoalWidth;
				}
			}
		}

		private void levelLose() {
			mGoalSpeed = getPadSpeedFromPreference();
			mHeading = 90;

			mFiring = false;
			mStopped = true;
			mBrickFallDownTimeElapsed = 0.0;
			mPadType = PILL_NOTHING;
			Log.d("______________", "level lose : "+ mPadType + " " + mOldPadType);
			cancelScale();
			mGoalX = (320 - mGoalWidth) / 2;
			//mGoalX = (mCanvasWidth - mGoalWidth) / 2;

			for (int i = 0; i < mBalls.size(); i++) {
				mBalls.get(i).mlive = false;
			}
			
			mPills.clear();
			mBullets.clear();
		}

		private void levelStart() {
			levelLose();
			setMatrix();
		}

		private void gameReset() {
			mGoalSpeed = getPadSpeedFromPreference();
			mHeading = 90;
			mLives = 3;
			mScore = 0;
			mLevel = 1;

			mFiring = false;
			mStopped = true;
			mBounceTime = 0;
			mBrickFallDownTimeElapsed = 0.0;
			mPadType = PILL_NOTHING;
			mGoalX = (320 - mGoalWidth) / 2;
			//mGoalX = (mCanvasWidth - mGoalWidth) / 2;

			for (int i = 0; i < mBalls.size(); i++) {
				mBalls.get(i).mlive = false;
			}
			
			mPills.clear();
			mBullets.clear();
			setMatrix();
		}

		private void setMatrix() {
			// Level 1 correspond to MATRIX[0][][]
			for (int i = 0; i < maxY; ++i) {
				for (int j = 0; j < maxX; j++) {
					matrix[i][j] = Level.MATRIX[mLevel - 1][i][j];
					matrix[i][j] = setBrickScore(matrix[i][j]);
				}
			}

			mBrickCount = Level.BRICK_COUNT[mLevel - 1];
		}


	class GameThread extends Thread {

		/** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
		public int mMode;
		private boolean mRun = false;
		private Handler mHandler;

		private static final String LOG_TAG = "GameThread";

		public GameThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
			mSurfaceHolder = surfaceHolder;
			mHandler = handler;
			mContext = context;
		}

		private void updateLife() {
			Message msg = mHandler.obtainMessage();
			Bundle b = new Bundle();
			msg.what = LIFE_STATUS; 
			b.putInt("life", mLives);
			msg.setData(b);
			mHandler.sendMessage(msg);
		}

		private void updateLevel() {
			Message msg = mHandler.obtainMessage();
			Bundle b = new Bundle();
			msg.what = LEVEL_STATUS; 
			b.putInt("level", mLevel);
			msg.setData(b);
			mHandler.sendMessage(msg);
			
		}

		private void updateScore() {
			Message msg = mHandler.obtainMessage();
			Bundle b = new Bundle();
			msg.what = SCORE_STATUS; 
			b.putInt("score", mScore);
			msg.setData(b);
			mHandler.sendMessage(msg);
		}

		private void updateInfoBoard() {
			updateScore();
			updateLife();
			updateLevel();
		}

		private void addScore(int score) {
			mScore += score;
			updateScore();
		}

		public void doStart() {
			synchronized (mSurfaceHolder) {
				mFiring = false;
				mStopped = true;
				mHeading = 90;
				mBounceTime = 0;
				mBrickFallDownTimeElapsed = 0.0;

				mOldPadType = mPadType = PILL_NOTHING;
				mGoalX = (mCanvasWidth - mGoalWidth) / 2;

				mBalls.get(0).setValue(0.0, 0.0, mBallWidth / 2 + mGoalX
						+ mGoalWidth / 2, mBallHeight / 2 + mGoalHeight
						+ mBoardHeight, true);

				mLastTime = System.currentTimeMillis() + 100;
				setState(STATE_RUNNING);
			}
		}

		public void doShotBall() {
			if (mStopped) {
				double s = Math.hypot(PHYS_SPEED_INIT, PHYS_SPEED_INIT);
				for (int i = 0; i < mBalls.size(); i++) {
					if (mBalls.get(i).mlive) {
						mBalls.get(i).mDY = s * Math.sin(Math.toRadians(mHeading));
						mBalls.get(i).mDX = s * Math.cos(Math.toRadians(mHeading));
				        //Log.d(LOG_TAG, mHeading + " x: "+ mBalls.get(i).mDX+" Y: "+mBalls.get(i).mDY + " s:" + s);
						break; // only one ball, so we break
					}
				}
				mStopped = false;
			}
		}

		public void pause() {
			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING)
					setState(STATE_PAUSE);
			}
		}

		private synchronized void restoreMatrix(Bundle savedState) {
			for (int i = 0; i < maxY; i++) {
				matrix[i] = savedState.getIntArray(KEY_MATRIX_PREFIX + i);
			}
		}

		private synchronized void saveMatrix(Bundle map) {
			if (map != null) {
				for (int i = 0; i < maxY; i++) {
					map.putIntArray(KEY_MATRIX_PREFIX + i, matrix[i]);
				}
			}
		}

		private boolean mJustRestore = false;
		public synchronized void restoreState(Bundle savedState) {
			synchronized (mSurfaceHolder) {
		    Log.d("___________2", "restoreState");
				mRun = true;
				mMode = STATE_PAUSE;//savedState.getInt(KEY_MODE);
				//mMode = savedState.getInt(KEY_MODE);
				mThreadExitMode = mMode;
				
				mJustRestore = true;

				mRotating = 0;
				mFiring = false;
				mStopped = savedState.getBoolean(KEY_STOPPED);

				mHeading = savedState.getDouble(KEY_HEADING);
				mBallWidth = savedState.getInt(KEY_BALL_WIDTH);
				mBallHeight = savedState.getInt(KEY_BALL_HEIGHT);
				mGoalX = savedState.getInt(KEY_GOAL_X);
				mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);
				mBrickCount = savedState.getInt(KEY_BRICK_COUNT);
				mLaserTotalElapse = savedState.getDouble(KEY_LASER_TOTAL_ELAPSE);

				mBounceTime = savedState.getInt(KEY_BOUNCE_TIME);
				mBrickFallDownTimeElapsed = savedState.getDouble(KEY_BRICK_FALL_DOWN_TIME_ELAPSED);

				mScore = savedState.getInt(KEY_SCORE);
				mLives = savedState.getInt(KEY_LIVES);
				mLevel = savedState.getInt(KEY_LEVEL);
				updateInfoBoard();

			Log.d("______________", "restore : "+ mPadType + " " + mOldPadType);
				mPadType = savedState.getInt(KEY_PAD_TYPE);
				mOldPadType = savedState.getInt(KEY_OLD_PAD_TYPE);

				restoreMatrix(savedState);
				//restore mBullets
				mBullets.clear();
				int bulletCount = savedState.getInt(KEY_BULLETS_SIZE); 
				for (int i = 0; i < bulletCount; i++) {
					String mainKey = KEY_BULLET_MAINKEY_PREFIX + i;
					int _type = savedState.getInt(mainKey + Bullet.KEY_TYPE);
					int _x = savedState.getInt(mainKey + Bullet.KEY_X);
					int _y = savedState.getInt(mainKey + Bullet.KEY_Y);
					mBullets.add(new Bullet(_type, _x, _y));
				}
				//restore mBalls
				mBalls.clear();
				int ballCount = savedState.getInt(KEY_BALLS_SIZE); 
				for (int i = 0; i < ballCount; i++) {
					String mainKey = KEY_BALL_MAINKEY_PREFIX + i;
					double _dx = savedState.getDouble(mainKey + Ball.KEY_DX);
					double _dy = savedState.getDouble(mainKey + Ball.KEY_DY);
					double _x = savedState.getDouble(mainKey + Ball.KEY_X);
					double _y = savedState.getDouble(mainKey + Ball.KEY_Y);
					boolean _live = savedState.getBoolean(mainKey + Ball.KEY_LIVE);
					Ball ball = new Ball();
					ball.setValue(_dx, _dy, _x, _y, _live);
					mBalls.add(ball);
				}
				//restore mPills
				mPills.clear();
				int pillCount = savedState.getInt(KEY_PILLS_SIZE); 
				for (int i = 0; i < pillCount; i++) {
					String mainKey = KEY_PILL_MAINKEY_PREFIX + i;
					int _type = savedState.getInt(mainKey + Pill.KEY_TYPE);
					int _x = savedState.getInt(mainKey + Pill.KEY_X);
					int _y = savedState.getInt(mainKey + Pill.KEY_Y);
					mPills.add(new Pill(_type, _x, _y));
				}
			}
		}

		public synchronized Bundle saveState(Bundle map) {
			synchronized (mSurfaceHolder) {
				if (map != null) {
		    		Log.d("___________", "saveState");

					map.putInt(KEY_MODE, mMode);
					map.putBoolean(KEY_STOPPED, mStopped);
					map.putDouble(KEY_HEADING, mHeading);
					map.putInt(KEY_BALL_WIDTH, mBallWidth);
					map.putInt(KEY_BALL_HEIGHT, mBallHeight);
					map.putInt(KEY_GOAL_X, mGoalX);
					map.putInt(KEY_GOAL_WIDTH, mGoalWidth);
					map.putInt(KEY_BRICK_COUNT, mBrickCount);
					map.putDouble(KEY_LASER_TOTAL_ELAPSE, mLaserTotalElapse);
				
					map.putInt(KEY_BOUNCE_TIME, mBounceTime);
					map.putDouble(KEY_BRICK_FALL_DOWN_TIME_ELAPSED, mBrickFallDownTimeElapsed);

					map.putInt(KEY_SCORE, mScore);
					map.putInt(KEY_LIVES, mLives);
					map.putInt(KEY_LEVEL, mLevel);
					map.putInt(KEY_PAD_TYPE, mPadType);
					map.putInt(KEY_OLD_PAD_TYPE, mOldPadType);

			Log.d("______________", "save: "+ mPadType + " " + mOldPadType);
					saveMatrix(map);

					//save mBullets
					map.putInt(KEY_BULLETS_SIZE, mBullets.size());
					for (int i = 0; i < mBullets.size(); i++) {
						Bullet bullet = mBullets.get(i);
						bullet.saveState(map, KEY_BULLET_MAINKEY_PREFIX + i);
					}
					//save mBalls
					map.putInt(KEY_BALLS_SIZE, mBalls.size());
					for (int i = 0; i < mBalls.size(); i++) {
						Ball ball = mBalls.get(i);
						ball.saveState(map, KEY_BALL_MAINKEY_PREFIX + i);
					}
					//save mPills
					map.putInt(KEY_PILLS_SIZE, mPills.size());
					for (int i = 0; i < mPills.size(); i++) {
						Pill pill = mPills.get(i);
						pill.saveState(map, KEY_PILL_MAINKEY_PREFIX + i);
					}
				}
			}
			return map;
		}

		@Override
		public void run() {
			while (mRun) {
				Canvas c = null;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						if (mMode == STATE_RUNNING)
							updatePhysics();
						doDraw(c);
					}
				} finally {
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
			}
		}

		public void refresh() {
			Canvas c = null;
			try {
				c = mSurfaceHolder.lockCanvas(null);
				synchronized (mSurfaceHolder) {
					doDraw(c);
				}
			} finally {
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}

		public void setRunning(boolean b) {
			mRun = b;
		}

		public boolean isRunning() {
			return mRun;
		}

		public void setState(int mode) {
			synchronized (mSurfaceHolder) {
				mMode = mode;

				if (mMode == STATE_RUNNING) {
					Message msg = mHandler.obtainMessage(GAME_STATUS);
					Bundle b = new Bundle();
					b.putString("text", "");
					b.putInt("viz", View.INVISIBLE);
					msg.setData(b);
					mHandler.sendMessage(msg);
				} else {
					Resources res = mContext.getResources();
					String message = null;
					mRotating = 0;
					mFiring = false;
					if (mMode == STATE_READY) {
						message = res.getString(R.string.mode_ready);
					} else if (mMode == STATE_PAUSE) {
						message = res.getString(R.string.mode_pause);
					} else if (mMode == STATE_LOSE_LEVEL) {
						message = res.getString(R.string.message_life_over);
					} else if (mMode == STATE_LOSE_GAME) {
						message = res.getString(R.string.message_game_over);
					} else if (mMode == STATE_WIN_LEVEL) {
						message = res.getString(R.string.mode_win_level);
					} else if (mMode == STATE_WIN_GAME) {
						message = res.getString(R.string.mode_win_game);
					}

					if (message != null) {
						Message msg = mHandler.obtainMessage(GAME_STATUS);
						Bundle b = new Bundle();
						b.putString("text", message);
						b.putInt("viz", View.VISIBLE);
						msg.setData(b);
						mHandler.sendMessage(msg);
					}
				}
			}
		}

		public void setSurfaceSize(int width, int height) {
			synchronized (mSurfaceHolder) {
				mCanvasWidth = width;
				mCanvasHeight = height;
				mBackgroundImage = mBackgroundImage.createScaledBitmap(
						mBackgroundImage, width, height, true);
			}
		}

		public void unpause() {
			synchronized (mSurfaceHolder) {
				mLastTime = System.currentTimeMillis() + 100;
			}
			setState(STATE_RUNNING);
		}

		boolean doKeyDown(int keyCode, KeyEvent msg) {
			synchronized (mSurfaceHolder) {
				boolean okStart = false;
				if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_S)
					okStart = true;

				if (okStart && mMode == STATE_READY) {
					// ready-to-start -> start
					doStart();
					return true;
				} else if (okStart  && mMode == STATE_WIN_LEVEL) {
					levelStart();
					updateInfoBoard();
					doStart();
					return true;
				} else if (okStart  && mMode == STATE_WIN_GAME) {
					gameReset();
					updateInfoBoard();
					doStart();
					return true;
				} else if (okStart  && mMode == STATE_LOSE_LEVEL) {
					levelLose();
					updateInfoBoard();
					doStart();
					return true;
				} else if (okStart && mMode == STATE_LOSE_GAME) {
					gameReset();
					updateInfoBoard();
					doStart();
					return true;
				} else if (mMode == STATE_PAUSE && okStart) {
					// paused -> running
					unpause();
					return true;
				} else if (mMode == STATE_RUNNING) {
					// center/space -> fire
					if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
							|| keyCode == KeyEvent.KEYCODE_SPACE) {
						setFiring(true && (mPadType == PILL_LASER || mPadType == PILL_ROCKET));
						doShotBall();
						return true;
						// left/q -> left
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
							|| keyCode == KeyEvent.KEYCODE_Q) {
						mRotating = -1;
						applyReverse();
						return true;
						// right/w -> right
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
							|| keyCode == KeyEvent.KEYCODE_W) {
						mRotating = 1;
						applyReverse();
						return true;
						// up -> pause
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
						pause();
						return true;
					}
				}

				return false;
			}
		}

		void operationForTouch() {
			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING) {
					setFiring(true && (mPadType == PILL_LASER || mPadType == PILL_ROCKET));
				}
			}
		}

		boolean operationForClick() {
			synchronized (mSurfaceHolder) {
				/*boolean okStart = false;
				if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_S)
					okStart = true;*/

				if (mMode == STATE_READY) {
					doStart();
					return true;
				} else if (mMode == STATE_WIN_LEVEL) {
					levelStart();
					updateInfoBoard();
					doStart();
					return true;
				} else if (mMode == STATE_WIN_GAME) {
					gameReset();
					updateInfoBoard();
					doStart();
					return true;
				} else if (mMode == STATE_LOSE_LEVEL) {
					levelLose();
					updateInfoBoard();
					doStart();
					return true;
				} else if (mMode == STATE_LOSE_GAME) {
					gameReset();
					updateInfoBoard();
					doStart();
					return true;
				} else if (mMode == STATE_PAUSE) {
					unpause();
					return true;
				} else if (mMode == STATE_RUNNING) {
					//setFiring(true && (mPadType == PILL_LASER || mPadType == PILL_ROCKET));
					doShotBall();
					return true;
				}
				return false;
			}
		}

		public void setFiring(boolean b) {
			mFiring = b;
		}

		boolean doKeyUp(int keyCode, KeyEvent msg) {
			boolean handled = false;

			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING) {
					if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
							|| keyCode == KeyEvent.KEYCODE_SPACE) {
						setFiring(false);
						handled = true;
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
							|| keyCode == KeyEvent.KEYCODE_Q
							|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
							|| keyCode == KeyEvent.KEYCODE_W) {
						mRotating = 0;
						handled = true;
					}
				}
			}

			return handled;
		}

		private void drawPad(Canvas canvas) {
			Resources res = mContext.getResources();
			Drawable d = null;
			switch (mPadType) {
			case PILL_LASER:
				d = res.getDrawable(R.drawable.pad_laser);
				break;
			case PILL_SCALE:
				d = res.getDrawable(R.drawable.pad_scale);
				break;
			case PILL_ROCKET:
				d = res.getDrawable(R.drawable.pad_rocket);
				break;
			default:
				d = res.getDrawable(R.drawable.pad);
				break;
			}
			mPadImage = d;
			/*mPadImage.setBounds(mGoalX, mCanvasHeight - mGoalHeight
					- mBoardHeight, mGoalX + mGoalWidth, mCanvasHeight
					- mBoardHeight);*/
			mPadImage.setBounds(mGoalX, mCanvasHeight - mGoalHeight , mGoalX + mGoalWidth, mCanvasHeight);
			mPadImage.draw(canvas);
		}

		private void drawBrickMatrix(Canvas canvas) {
			int i, j;
			//mBrickImage1.draw(canvas);
			//mBrickImage1.setVisible(false, false);
			for (i = 0; i < maxY; ++i)
				for (j = 0; j < maxX; ++j) {
					switch (getBrickType(matrix[i][j])) {
					case BRICK_TYPE_ONE:
						//mBrickImage1.resetTransition();
						mBrickImage1.setBounds(j * mBrickWidth, mBrickOffsetY
								+ i * mBrickHeight, (j + 1) * mBrickWidth,
								mBrickOffsetY + (i + 1) * mBrickHeight);
						mBrickImage1.draw(canvas);
						break;
					case BRICK_TYPE_TWO:
						//mBrickImage2.resetTransition();
						mBrickImage2.setBounds(j * mBrickWidth, mBrickOffsetY
								+ i * mBrickHeight, (j + 1) * mBrickWidth,
								mBrickOffsetY + (i + 1) * mBrickHeight);
						mBrickImage2.draw(canvas);
						break;
					case BRICK_TYPE_THREE:
						//mBrickImage3.resetTransition();
						mBrickImage3.setBounds(j * mBrickWidth, mBrickOffsetY
								+ i * mBrickHeight, (j + 1) * mBrickWidth,
								mBrickOffsetY + (i + 1) * mBrickHeight);
						mBrickImage3.draw(canvas);
						break;
					case BRICK_TYPE_FOUR:
						//mBrickImage4.resetTransition();
						mBrickImage4.setBounds(j * mBrickWidth, mBrickOffsetY
								+ i * mBrickHeight, (j + 1) * mBrickWidth,
								mBrickOffsetY + (i + 1) * mBrickHeight);
						mBrickImage4.draw(canvas);
						break;
					case BRICK_TYPE_FIVE:
						//mBrickImage5.resetTransition();
						mBrickImage5.setBounds(j * mBrickWidth, mBrickOffsetY
								+ i * mBrickHeight, (j + 1) * mBrickWidth,
								mBrickOffsetY + (i + 1) * mBrickHeight);
						mBrickImage5.draw(canvas);
						break;
					default:
						break;
					}
				}
		}

		private void bulletHitBrick() {
			for (int i = 0; i < mBullets.size();) {
				Bullet bullet = mBullets.get(i);
				int indexX = (new Double(bullet.x / mBrickWidth)).intValue();
				int indexY = (new Double((mCanvasHeight - mBrickOffsetY - bullet.y - bullet.h / 2) / mBrickHeight)).
						intValue();

				if (entityHitBrick(indexX, indexY, true, bullet.type)) {
					if (mBullets.get(i).type == Bullet.LASER) {
						addScore(LASER_SCORE);
						matrix[indexY][indexX] = setLaserBrickScoreAndType(matrix[indexY][indexX]);
						GameActivity.getSoundService().playSound(LocalService.SOUND_LASER_HIT_BRICK, true);
					}
					else {
						addScore(ROCKET_SCORE);
						GameActivity.getSoundService().playSound(LocalService.SOUND_ROCKET_HIT_BRICK, true);
					}	
					mBullets.remove(i);
					continue;
				}
				i++;
			}
		}

		private void ballHitBrick() {
			for (int i = 0; i < mBalls.size(); i++) {
				if (mBalls.get(i).mlive) {
					int indexX = (new Double(mBalls.get(i).mX / mBrickWidth)).intValue();
					int indexY = (new Double((mCanvasHeight - mBrickOffsetY - mBalls.get(i).mY)
									/ mBrickHeight)).intValue();
					if (!entityHitBrick(indexX, indexY, true, ENTITY_BALL))
						return;
					applyBombExplode(indexX, indexY);
					addScore(BALL_SCORE);
					if (getBrickType(matrix[indexY][indexX]) != BRICK_TYPE_INVALID) {
						// BRICK_TYPE_INVALID means, before ball hit the brick, brick type is BRICK_TYPE_ONE
						GameActivity.getSoundService().playSound(LocalService.SOUND_BALL_HIT_BRICK, true);
					}
					
					//calculate original
					long now = System.currentTimeMillis();
					if (mLastTime > now)
						return;
					double elapsed = (now - mLastTime) / 1000.0;
					double x = mBalls.get(i).mX;
					double y = mBalls.get(i).mY;
					double oldx = x - elapsed * mBalls.get(i).mDX;
					double oldy = y - elapsed * mBalls.get(i).mDY;

					if (XY(indexX, indexY, mBalls.get(i).mDX, mBalls.get(i).mDY, x, y, oldx, oldy)) {
						mBalls.get(i).mDY = -mBalls.get(i).mDY;
					} else {
						mBalls.get(i).mDX = -mBalls.get(i).mDX;
					}
				}
			}
		}

		private boolean entityHitBrick(int indexX, int indexY, boolean playSound, 
			int entityType) {
			if (indexX < 0 || indexX >= maxX || indexY < 0
					|| indexY >= maxY)
				return false;
			if (getBrickType(matrix[indexY][indexX]) == BRICK_TYPE_INVALID)
				return false;
			//TransitionDrawable d = null;
			Drawable d = null;
		
			int v = matrix[indexY][indexX];
			
			if (entityType != ENTITY_ROCKET) {
				switch (getBrickType(v)) {
				case BRICK_TYPE_ONE:
					d = mBrickImage1;
					GameActivity.getSoundService().playSound(LocalService.SOUND_BALL_HIT_BRICK, playSound);
					--mBrickCount;
					break;
				case BRICK_TYPE_TWO:
					d = mBrickImage2;
					break;
				case BRICK_TYPE_THREE:
					d = mBrickImage3;
					break;
				case BRICK_TYPE_FOUR:
					d = mBrickImage4;
					break;
				case BRICK_TYPE_FIVE:
					d = mBrickImage5;
					break;
				}
				d.setBounds(indexX * mBrickWidth, mBrickOffsetY + indexY * mBrickHeight, 
						(indexX + 1) * mBrickWidth, mBrickOffsetY + (indexY + 1) * mBrickHeight);
            
				//d.startTransition(200);
				if (getStep(v) == getBrickType(v)) {
					Pill p = new Pill(getPillType(v), indexX * mBrickWidth
							+ mBrickWidth / 2, mCanvasHeight
							- (mBrickOffsetY + (indexY + 1) * mBrickHeight));
					mPills.add(p);
				}
				if (getBrickType(v) != BRICK_TYPE_FIVE)
					--matrix[indexY][indexX];
			} else {
				//todo : rocket broke the brick
				matrix[indexY][indexX] = 0;
				int pillType = getPillType(v);
				if (pillType != PILL_NOTHING) {
					Pill p = new Pill(pillType, indexX * mBrickWidth
						+ mBrickWidth / 2, mCanvasHeight
						- (mBrickOffsetY + (indexY + 1) * mBrickHeight));
					mPills.add(p);
				}
			}
			
			if (mBrickCount == 0) {
				Resources res = mContext.getResources();
				GameActivity.getSoundService().playSound(LocalService.SOUND_WIN, true);
				mLevel++;
				if (mLevel > Level.BRICK_COUNT.length) { // pass all the level
					BestRecord.saveRecord(mContext, mScore);
					setState(STATE_WIN_GAME);
					return true;
				}
				setState(STATE_WIN_LEVEL);
				updateLevel();
			}
			return true;
		}

		private void doDraw(Canvas canvas) {
			canvas.drawBitmap(mBackgroundImage, 0, 0, null);

			drawBrickMatrix(canvas);
			drawPad(canvas);

			for (int i = 0; i < mBullets.size(); i++) {
				final Bullet b = (Bullet) mBullets.get(i);
				final Drawable d = b.bulletImage;
				d.setBounds(b.x - b.w / 2, mCanvasHeight - b.y - b.h
						/ 2, b.x + b.w / 2, mCanvasHeight - b.y + b.h
						/ 2);
				d.draw(canvas);
			}

			for (int i = 0; i < mPills.size(); i++) {
				final Pill p = (Pill) mPills.get(i);
				final Drawable d = p.pillImage;
				d.setBounds(p.x - p.w / 2, mCanvasHeight - p.y - p.h
						/ 2, p.x + p.w / 2, mCanvasHeight - p.y + p.h
						/ 2);
				d.draw(canvas);
			}

			for (int i = 0; i < mBalls.size(); i++)
				if (mBalls.get(i).mlive) {
					int yTop = mCanvasHeight
							- ((int) mBalls.get(i).mY + mBallHeight / 2);
					int xLeft = (int) mBalls.get(i).mX - mBallWidth / 2;

					if (mStopped) {
						canvas.save();
                        double angle = angleConvert(); 
						canvas.rotate((float) angle, (float) mBalls.get(i).mX,
							mCanvasHeight - (float) mBalls.get(i).mY);
						/* Green line Arrow
						Paint linePaint;
						linePaint = new Paint();
						linePaint.setAntiAlias(true);
						linePaint.setARGB(255, 0, 255, 0);
						canvas.drawLine((float) mBalls.get(i).mX, 1 + mCanvasHeight
							- (float) mBalls.get(i).mY, (float) mBalls.get(i).mX, 1
							+ mCanvasHeight - (float) mBalls.get(i).mY
							- POINTER_LENGTH, linePaint);*/
						int arrowWidth = mArrowImage.getIntrinsicWidth();
						int arrowHeight = mArrowImage.getIntrinsicHeight();
						mArrowImage.setBounds(
								(int) (mBalls.get(i).mX - arrowWidth / 2), 
								(int) (1 + mCanvasHeight - mBalls.get(i).mY - arrowHeight), 
								(int) (mBalls.get(i).mX + arrowWidth / 2),
								(int) (1 + mCanvasHeight - mBalls.get(i).mY));
						mArrowImage.draw(canvas);
						canvas.restore();
					}

					mBallImage[i].setBounds(xLeft, yTop, xLeft + mBallWidth, yTop + mBallHeight);
					mBallImage[i].draw(canvas);
				}
		}

		private double angleConvert() {
			if (45 <= mHeading && mHeading <= 90)
				return 90 - mHeading;
            else if (90 < mHeading && mHeading <= 135)
				return 450 - mHeading;
			return 30;
        }

		private double[] getIntersection(double x1, double y1, 
										double x2, double y2,
										double x3, double y3,
										double x4, double y4) {
			double[] res = new double[2];
 			res[0] = ((x1 - x2) * (x3 * y4 - x4 * y3) - (x3 - x4) * (x1 * y2 - x2 * y1))  
     				/ ((x3 - x4) * (y1 - y2) - (x1 - x2) * (y3 - y4));
			res[1] = ((y1 - y2) * (x3 * y4 - x4 * y3) - (x1 * y2 - x2 * y1) * (y3 - y4))  
     				/ ((y1 - y2) * (x3 - x4) - (x1 - x2) * (y3 - y4));  
			return res;
		}

		/**
		Return true, change Y direction speed
			   false, change X direction speed
		*/
		private boolean XY(	int indexX, int indexY,
							double speedX, double speedY,
							double x1, double y1, 
							double x2, double y2) {
			if (speedX == 0.0)
				return true;

			double res[] = new double[2];

			int left = indexX * mBrickWidth;
			int right = (indexX + 1) * mBrickWidth;

			int top = mCanvasHeight - (mBrickOffsetY + indexY * mBrickHeight);
			int bottom = mCanvasHeight - (mBrickOffsetY + (indexY + 1) * mBrickHeight);
			
			if (speedY < 0.0 && speedX < 0.0) { 
				res = getIntersection(x1, y1, x2, y2, left, top, right, top); //top
				if (left <= res[0] && res[0]<= right)
					return true;
				res = getIntersection(x1, y1, x2, y2, right, top, right, bottom); //right
				if (bottom <= res[1] && res[1] <= top)
					return false;
			} else if (speedY < 0.0 && speedX > 0.0) { 
				res = getIntersection(x1, y1, x2, y2, left, top, right, top); //top
				if (left <= res[0] && res[0]<= right)
					return true;
				res = getIntersection(x1, y1, x2, y2, left, top, left, bottom); //left
				if (bottom <= res[1] && res[1] <= top)
					return false;
			} else if (speedY > 0.0 && speedX < 0.0) { 
				res = getIntersection(x1, y1, x2, y2, left, bottom, right, bottom); //bottom
				if (left <= res[0] && res[0]<= right) {
					return true;
				}
				res = getIntersection(x1, y1, x2, y2, right, top, right, bottom); //right
				if (bottom <= res[1] && res[1] <= top) {
					return false;
				}
			} else if (speedY > 0.0 && speedX > 0.0) { 
				res = getIntersection(x1, y1, x2, y2, left, bottom, right, bottom); //bottom
				if (left <= res[0] && res[0]<= right)
					return true;
				res = getIntersection(x1, y1, x2, y2, left, top, left, bottom); //left
				if (bottom <= res[1] && res[1] <= top)
					return false;
			}
			return true;
		}

		private void applyWall() {
			cancelScale();
			if (mPadType != PILL_WALL) {
				if (mGoalX < 0)
					mGoalX = 0;
				else if (mGoalX + mGoalWidth > mCanvasWidth - 1)
					mGoalX = mCanvasWidth - mGoalWidth - 1;
			} else {
				if (mGoalX + mGoalWidth < 0)
					mGoalX = mCanvasWidth - 1;
				else if (mGoalX > mCanvasWidth - 1)
					mGoalX = -mGoalWidth + 1;
			}
		}

		public void applyReverse() {
			cancelScale();
			if (mPadType == PILL_REVERSE) {
				mRotating = -mRotating;
			}
		}

		private void applyLife() {
			cancelScale();
			if (mPadType == PILL_LIFE) {
				mLives++;
				mPadType = PILL_NOTHING;
				updateLife();
			}
		}

		private void applySlow() {
			cancelScale();
			if (mPadType == PILL_SLOW) {
				for (int i = 0; i < mBalls.size(); i++)
					if (mBalls.get(i).mlive) {
						if (Math.abs(mBalls.get(i).mDX) > PHYS_SPEED_MIN) {
							int flag = (mBalls.get(i).mDX < 0) ? -1 : 1;
							mBalls.get(i).mDX = flag * PHYS_SPEED_MIN;
						}
						if (Math.abs(mBalls.get(i).mDY) > PHYS_SPEED_MIN) {
							int flag = (mBalls.get(i).mDY < 0) ? -1 : 1;
							mBalls.get(i).mDY = flag * PHYS_SPEED_MIN;
						}
					}
			}
		}

		private void applyBullet() {
			cancelScale();
		}

		private void applyBomb() {
			/*cancelScale();
			if (mShortcutHandler == null)
                mShortcutHandler = new Handler();
            
			mShortcutHandler.postDelayed(new Runnable() {
				@Override
				public void run() {
					if (mPadType == PILL_BOMB) {
						reverseTransition(200);
						
					}
				}
            }, 100);*/
	
		}

		private void applyScale() {
			if (mPadType == PILL_SCALE) {
				// test with through wall
				mOldPadType = mPadType;
				mPadImage = mContext.getResources().getDrawable(
						R.drawable.pad_scale);
				mGoalWidth = mPadImage.getIntrinsicWidth();
				mGoalHeight = mPadImage.getIntrinsicHeight();
				if (mGoalX <= 0) {
					mGoalX = 0;
				} else if (mGoalX + mGoalWidth >= mCanvasWidth - 1) {
					mGoalX = mCanvasWidth - 1 - mGoalWidth;
				}
			}
		}

		private void applyBallStop() {
			cancelScale();
			if (mPadType == PILL_STOP) {
				mStopped = true;
				for (int i = 0; i < mBalls.size(); i++)
					if (mBalls.get(i).mlive) {
						mBalls.get(i).setValue(0.0, 0.0, (new Double(mBalls.get(i).mX))
								.intValue(), mBallHeight / 2 + mGoalHeight
								+ mBoardHeight, true);
					}
			}
		}
		
		private void applyBombExplode(int indexX, int indexY) {
			cancelScale();
			int i, j;
			if (mPadType == PILL_BOMB) {
				int brickCoundBeforeBombExplosion = mBrickCount;
				i = indexY;
				j = indexX - 1;
				entityHitBrick(j, i, false, ENTITY_BOMB);
				i = indexY;
				j = indexX + 1;
				entityHitBrick(j, i, false, ENTITY_BOMB);
				i = indexY - 1;
				j = indexX;
				entityHitBrick(j, i, false, ENTITY_BOMB);
				i = indexY + 1;
				j = indexX;
				entityHitBrick(j, i, false, ENTITY_BOMB);
				addScore((mBrickCount - brickCoundBeforeBombExplosion) * BOMB_SCORE);
				GameActivity.getSoundService().playSound(LocalService.SOUND_BOMB_EXPLODE, true);
				mPadType = PILL_NOTHING;
			}
		}

		private void applySplit() {
			cancelScale();
			if (mPadType == PILL_SPLIT) {
				double x, y, v ;
				x = y = v = 0.0;
				for (int i = 0; i < mBalls.size(); i++) {
					if (mBalls.get(i).mlive) {
						v = Math.hypot(mBalls.get(i).mDX, mBalls.get(i).mDY);
                	    x = mBalls.get(i).mX;
                    	y = mBalls.get(i).mY;
						break;
					}
	            }

    	        mBalls.get(0).setValue(v * Math.cos(Math.toRadians(45)), v * Math.sin(Math.toRadians(45)), x, y, true);
        	    mBalls.get(1).setValue(v * Math.cos(Math.toRadians(90)), v * Math.sin(Math.toRadians(75)), x, y, true);
            	mBalls.get(2).setValue(v * Math.cos(Math.toRadians(105)), v * Math.sin(Math.toRadians(105)), x, y, true);
	            mBalls.get(3).setValue(v * Math.cos(Math.toRadians(135)), v * Math.sin(Math.toRadians(135)), x, y, true);

				mPadType = PILL_NOTHING;
			}
		}

		private boolean isAllDead() {
			for (int i = 0; i < mBalls.size(); i++)
				if (mBalls.get(i).mlive)
					return false;
			return true;
		}	

		private void applyBrickFallDown(double elapsed) {
			++mBounceTime;
			if (mBounceTime > BOUNCE_THRESHOLD) {
				if (mBrickFallDownTimeElapsed > FALL_DOWN_TIME_THRESHOLD && 
						getPadAndBrickRowDistance(getLowestBrickRowIndex()) /
							mBrickHeight > FALL_DOWN_DISTANCE_THRESHOLD) {
					mBrickFallDownTimeElapsed = 0.0;
					mBrickOffsetY += mBrickHeight;
				}
			}
		}

		private int getPadAndBrickRowDistance(int row) {
			int brickBottom = mBrickOffsetY + (row + 1) * mBrickHeight;
			int padTop = mCanvasHeight - mGoalHeight;
			return padTop - brickBottom;
		}

		private int getLowestBrickRowIndex() {
			int i, j;
			for (i = maxY - 1; i >= 0; --i)
				for (j = 0; j < maxX; ++j) {
					if (getBrickType(matrix[i][j]) != BRICK_TYPE_INVALID) {
						return i;
					}
				}
			return 0;
		}

		private void updatePhysics() {
			long now = System.currentTimeMillis();
			if (mLastTime > now)
				return;

			if (mJustRestore) {
				mJustRestore = false;
				mLastTime = now;
				return;
			}

			double elapsed = (now - mLastTime) / 1000.0;
			mLastTime = now;

			mLaserTotalElapse += elapsed;

			mBrickFallDownTimeElapsed += elapsed;

			if (mRotating != 0) {
				if (mStopped) {
					mHeading += -mRotating * (PHYS_SLEW_SEC * elapsed);
                    if (mHeading >= 360 ) mHeading = mHeading % 360;
                    else if (mHeading < 0) mHeading = 360 - Math.abs(mHeading) % 360;
				} else {
					mGoalX += mRotating * (mGoalSpeed * elapsed);
				}
				if (mHeading < ANGLE_MIN)
					mHeading = ANGLE_MIN;
				else if (mHeading > ANGLE_MAX)
					mHeading = ANGLE_MAX;

				applyWall();
			}

			// update already shooted
			for (int i = 0; i < mBullets.size();) {
				Bullet bullet = mBullets.get(i);
				bullet.y += bullet.speed * elapsed;
				if (bullet.y > mCanvasHeight + bullet.h / 2) { // remove out of window
					mBullets.remove(i);
					continue;
				}
				i++;
			}

			// update pill
			for (int i = 0; i < mPills.size();) {
				Pill pill = mPills.get(i);
				pill.y -= PILL_FALL_DOWN_SPEED * elapsed;
				if ((pill.y + pill.h / 2 < mGoalHeight) &&
					(mGoalX - pill.w / 2 < pill.x && pill.x < mGoalX + mGoalWidth + pill.w / 2)) {
					mPadType = pill.type;
					applyBullet();
					applySlow();
					applyScale();
					applyLife();
					applyBomb();
					mPills.remove(i);
					GameActivity.getSoundService().playSound(LocalService.SOUND_GET_PILL, true);
					addScore(PILL_SCORE);
					continue;
				}

				if (pill.y + pill.h / 2 < mGoalHeight) {
					mPills.remove(i);
					continue;
				}
				i++;
			}

			if (mFiring) {
				if (mLaserTotalElapse > LASERSHOTTHRESHOLD) {
					mLaserTotalElapse = 0.0;
					if (mPadType == PILL_LASER) {
						Bullet newBullet = new Bullet(Bullet.LASER, mGoalX
								+ mGoalWidth / 4, mGoalHeight + 10);
						mBullets.add(newBullet);

						newBullet = new Bullet(Bullet.LASER, mGoalX
								+ 3 * mGoalWidth / 4, mGoalHeight + 10);
						mBullets.add(newBullet);
						GameActivity.getSoundService().playSound(LocalService.SOUND_SHOOT_LASER, true);

					} else if (mPadType == PILL_ROCKET) {
						Bullet newBullet = new Bullet(Bullet.ROCKET, mGoalX
								+ mGoalWidth / 2, mGoalHeight + 10);
						mBullets.add(newBullet);
						GameActivity.getSoundService().playSound(LocalService.SOUND_SHOOT_ROCKET, true);
					}
				}
			}

			if (!mStopped) {
				for (int i = 0; i < mBalls.size(); i++) {
					if (mBalls.get(i).mlive) {
						mBalls.get(i).mX += elapsed * mBalls.get(i).mDX;
						mBalls.get(i).mY += elapsed * mBalls.get(i).mDY;
					}
				}

				ballHitBrick();
				bulletHitBrick();

				double yUpperBound = mCanvasHeight - mBallHeight / 2;
				double xLeftBound = mBallWidth / 2;
				double xRightBound = mCanvasWidth - mBallWidth / 2;
				double yLowerBound = mGoalHeight + mBallHeight / 2;

				// ball become faster and faster
				for (int i = 0; i < mBalls.size(); i++) {
					if (mBalls.get(i).mlive) {

						int flag = (mBalls.get(i).mDX < 0) ? -1 : 1; 
						mBalls.get(i).mDX = flag * ((Math.abs(mBalls.get(i).mDX) < PHYS_SPEED_MAX) ? 
							Math.abs(mBalls.get(i).mDX) + DELTA_SPEED_X : PHYS_SPEED_MAX);

						flag = (mBalls.get(i).mDY < 0) ? -1 : 1; 
						mBalls.get(i).mDY = flag * ((Math.abs(mBalls.get(i).mDY) < PHYS_SPEED_MAX) ? 
							Math.abs(mBalls.get(i).mDY) + DELTA_SPEED_Y : PHYS_SPEED_MAX);
					}
				}

				for (int i = 0; i < mBalls.size(); i++) {
					if (mBalls.get(i).mlive) {
						if (mBalls.get(i).mX >= xRightBound) {
							mBalls.get(i).mX = xRightBound;
					        mBalls.get(i).mDX = -mBalls.get(i).mDX;
						} else if (mBalls.get(i).mX <= xLeftBound) {
							mBalls.get(i).mX = xLeftBound;
					        mBalls.get(i).mDX = -mBalls.get(i).mDX;
						}

						if (mBalls.get(i).mY >= yUpperBound) {
							mBalls.get(i).mY = yUpperBound;
							mBalls.get(i).mDY = -mBalls.get(i).mDY;
						} else if (mBalls.get(i).mY <= yLowerBound) {
							mBalls.get(i).mY = yLowerBound;
							mBalls.get(i).mDY = -mBalls.get(i).mDY;

							Resources res = mContext.getResources();
							int result = STATE_RUNNING;
							boolean onGoal = (mGoalX <= mBalls.get(i).mX && mBalls.get(i).mX <= mGoalX
									+ mGoalWidth);
							if (onGoal) {
								applyBrickFallDown(elapsed);
								mBalls.get(i).mDX += mRotating * (PHYS_PAD_SEC * elapsed);
								applyBallStop();
					            applySplit();
								GameActivity.getSoundService().playSound(LocalService.SOUND_BALL_BOUNCE, true);
							} else if (!onGoal) {
                                mBalls.get(i).mlive = false;
								if (isAllDead()) {
									--mLives;
									updateLife();
									if (mLives == 0) { // no life
										BestRecord.saveRecord(mContext, mScore);
										result = STATE_LOSE_GAME;
									} else {
										result = STATE_LOSE_LEVEL;
									}
								}
							}
							setState(result);
						}
					}
				}
			}// updatePhysics
		}
	}

	private static final int GAME_STATUS = 1;
	private static final int SCORE_STATUS = 2;
	private static final int LIFE_STATUS = 3;
	private static final int LEVEL_STATUS = 4;

	private Context mContext;
	private TextView mStatusText;
	private GameThread thread;
	private int mBoardHeight;
	private GameActivity mGameActivity;
	
	private TextView mScoreText;
	private TextView mLifeText;
	private TextView mLevelText;
	private Button[] mControlButton;
	private Handler mHandler;

	private int mThreadExitMode = STATE_FIRST_START;

	public GameView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		SurfaceHolder holder = getHolder();
		thread = createThread(holder, context);
		mHandler = new Handler();
		setFocusable(true); // make sure we get key events
		loadResource();
	}

	public void setGameActivity(GameActivity l) {
		mGameActivity = l;
	}

	private GameThread createThread(SurfaceHolder holder, Context context) {
		holder.addCallback(this);
		return new GameThread(holder, context, new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch(msg.what) {
				case GAME_STATUS:
					mStatusText.setVisibility(msg.getData().getInt("viz"));
					mStatusText.setText(msg.getData().getString("text"));
					break;
				case SCORE_STATUS:
					mScoreText.setText(msg.getData().getInt("score") + "");
					break;
				case LIFE_STATUS:
					mLifeText.setText(msg.getData().getInt("life") + "");
					break;
				case LEVEL_STATUS:
					mLevelText.setText(msg.getData().getInt("level") +  "");
					break;
				}
			}
		});
	}

	@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
	}

	public void setBoardHeight(int h) {
		mBoardHeight = h;
	}

	public GameThread getThread() {
		return thread;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent msg) {
		return thread.doKeyDown(keyCode, msg);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent msg) {
		return thread.doKeyUp(keyCode, msg);
	}

	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		if (!hasWindowFocus)
			thread.pause();
	}

	public void setTextView(TextView textView) {
		mStatusText = textView;
	}

	public void setScoreTextView(TextView textView) {
		mScoreText = textView;
	}

	public void setLifeTextView(TextView textView) {
		mLifeText = textView;
	}

	public void setLevelTextView(TextView textView) {
		mLevelText = textView;
	}

	public void setControlButton(Button[] btn) {
		mControlButton = new Button[btn.length];
		for (int i = 0; i < btn.length; i++)
			mControlButton[i] = btn[i];

		mControlButton[0].setOnTouchListener(new Button.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				if (thread != null && thread.isRunning()) {
					thread.operationForTouch();	
						//thread.setFiring(true && (mPadType == PILL_LASER || mPadType == PILL_ROCKET));
						//thread.doShotBall();
					/*mHandler.postDelayed(new Runnable() {
						public void run() {
							//if (!mControlButton[0].isPressed()) 
								//mRotating = 0;
						}
					}, 100);*/
				}
				return false;
			}
		});
	
		mControlButton[0].setOnClickListener(new Button.OnClickListener() {
			public void onClick(View v) {
				if (thread != null && thread.isRunning()) {
					thread.operationForClick();
				}
			}
		});
		
		mControlButton[1].setOnTouchListener(new Button.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				if (thread != null && thread.isRunning()) {
					mRotating = -1;
					thread.applyReverse();
					mHandler.postDelayed(new Runnable() {
						public void run() {
							if (!mControlButton[1].isPressed())
								mRotating = 0;
						}
					}, 100);
				}
				return false;
			}
		});
	
		mControlButton[2].setOnTouchListener(new Button.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				if (thread != null && thread.isRunning()) {
					mRotating = 1;
					thread.applyReverse();
					mHandler.postDelayed(new Runnable() {
						public void run() {
							if (!mControlButton[2].isPressed())
								mRotating = 0;
						}
					}, 100);
				}
				return false;
			}
		});
	
		/*mControlButton[1].setOnClickListener(new Button.OnClickListener() {
			public void onClick(View v) {
				if (thread != null && thread.isRunning()) {
					mRotating = -1;
					thread.applyReverse();
					mHandler.postDelayed(new Runnable() {
						public void run() {
							mRotating = 0;
						}
					}, 100);
				}
			}
		});

		mControlButton[1].setOnLongClickListener(new View.OnLongClickListener() {
			public boolean onLongClick(View v) {
			Log.d("____________","long click");
				if (thread != null && thread.isRunning()) {
					mRotating = -1;
					thread.applyReverse();
				}
				return true;
			}
		});

		mControlButton[2].setOnClickListener(new Button.OnClickListener() {
			public void onClick(View v) {
				if (thread != null && thread.isRunning()) {
					mRotating = 1;
					thread.applyReverse();
				}
			}
		});*/
		/*mControlButton[1].setOnTouchListener(new Button.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
			Log.d("____________","touching ...........");
				if (thread != null && thread.isRunning()) {
					mRotating = -1;
					thread.applyReverse();
					mHandler.postDelayed(new Runnable() {
						public void run() {
							mRotating = 0;
						}
					}, 100);
				}
				return true;
			}
		});
		
		mControlButton[1].setOnTouchListener(new Button.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
			Log.d("____________","touching left...........");
				if (thread != null && thread.isRunning()) {
					mRotating = -1;
					thread.applyReverse();
					mHandler.postDelayed(new Runnable() {
						public void run() {
							mRotating = 0;
						}
					}, 100);
				}
				return false;
			}
		});*/

	}

	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		/*if (width > height) { // land scape
			setVisibility(View.INVISIBLE);
				mStatusText.setVisibility(View.VISIBLE);
				mStatusText.setText(R.string.landscape_rotate_info);
			return;
		}*/
		thread.setSurfaceSize(width, height);
	}

	public void surfaceCreated(SurfaceHolder holder) {
	    Log.d("_________", "surface created");
		if (mThreadExitMode != STATE_FIRST_START) {
	    Log.d("_________1", "surface created");
			thread = createThread(holder, mContext);
			mGameActivity.setGameThread(thread);
			thread.setRunning(true);
			thread.start();
			thread.setState(mThreadExitMode);
			return;
		}
	    Log.d("_________2", "surface created");
		thread.setRunning(true);
		thread.start();
		thread.setState(STATE_READY);
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		thread.setRunning(false);
		mThreadExitMode = thread.mMode;
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}
	
	@Override
	protected void finalize() {
		//releaseSounds();
	}
}
